package com.black.utils.file.compress.tar;

import com.black.utils.file.FileTypeEnum;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;

import java.io.*;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import static com.black.utils.file.FileUtil.BUFFER_SIZE;

/**
 * @author ylx
 */
public class TarUtil {
    /**
     * 将指定路径的文件压缩成tar文件
     * 根据源文件名，自动生成压缩包名
     * 根据containsSelf，是否包含目录本身
     *
     * @param srcPath      源文件路径
     * @param containsSelf 是否包含目录本身
     * @throws IOException 运行时IO异常
     */
    public static void toTar(String srcPath, boolean containsSelf) throws IOException {
        toTar(new File(srcPath), containsSelf);
    }

    public static void toTar(File src, boolean containsSelf) throws IOException {
        if (!src.exists()) {
            throw new IOException("src not exists");
        }
        String absolutePath = src.getAbsolutePath();
        absolutePath = FileTypeEnum.transferExtension(absolutePath, FileTypeEnum.TAR);
        File tarFile = new File(absolutePath);
        tarFile.createNewFile();
        if (src.isDirectory() && !containsSelf) {
            toTar(tarFile, src.listFiles());
        } else {
            toTar(tarFile, src);
        }
    }

    public static void toTar(File dist, File... files) throws IOException {
        List<File> srcFiles = Arrays.asList(files);
        toTar(dist, srcFiles);
    }

    public static void toTar(File dist, String... files) throws IOException {
        List<File> srcFiles = Arrays.stream(files).map(File::new).collect(Collectors.toList());
        toTar(dist, srcFiles);
    }

    public static void toTar(String dist, File... files) throws IOException {
        toTar(new File(dist), files);
    }

    public static void toTar(String dist, String... files) throws IOException {
        toTar(new File(dist), files);
    }

    /**
     * 将文件列表递归添加到指定的tar文件中，保持目录结构
     *
     * @param tarFile  输出的tar文件
     * @param srcFiles 要压缩的文件列表
     * @throws IOException as
     */
    public static void toTar(File tarFile, List<File> srcFiles) throws IOException {
        if (CollectionUtils.isEmpty(srcFiles)) {
            throw new IOException("no files to tar");
        }
        try (FileOutputStream fos = new FileOutputStream(tarFile);
             BufferedOutputStream bos = new BufferedOutputStream(fos);
             TarArchiveOutputStream tarOut = new TarArchiveOutputStream(bos)) {

            tarOut.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);

            for (File file : srcFiles) {
                compress(file, tarOut, file.getParent());
            }
        }
    }

    /**
     * 递归地将文件或目录添加到tar输出流中
     *
     * @param tarOut   TarArchiveOutputStream输出流
     * @param file     要添加的文件或目录
     * @param basePath 用于保持目录结构的基础路径
     * @throws IOException IO异常
     */
    private static void compress(File file, TarArchiveOutputStream tarOut, String basePath) throws IOException {
        String entryName = basePath == null ? file.getName() : file.getPath().substring(basePath.length() + 1);
        TarArchiveEntry entry = new TarArchiveEntry(file, entryName);

        tarOut.putArchiveEntry(entry);

        if (file.isDirectory()) {
            tarOut.closeArchiveEntry();
            File[] children = file.listFiles();
            if (children != null) {
                for (File child : children) {
                    compress(child, tarOut, basePath);
                }
            }
        } else {
            try (FileInputStream fis = new FileInputStream(file)) {
                byte[] buffer = new byte[BUFFER_SIZE];
                int length;
                while ((length = fis.read(buffer)) != -1) {
                    tarOut.write(buffer, 0, length);
                }
            }
            tarOut.closeArchiveEntry();
        }
    }

    /**
     * UnTar a tar file to the same directory
     * 解压到当前目录
     *
     * @param tarFile zip压缩文件
     * @throws IOException 运行时IO异常
     */
    public static void unTar(String tarFile) throws IOException {
        unTar(new File(tarFile));
    }

    /**
     * 解压到与当前压缩包所在目录
     *
     * @param tarFile zip压缩文件
     * @throws IOException 运行时IO异常
     */
    public static void unTar(File tarFile) throws IOException {
        unTar(tarFile, true);
    }

    public static void unTar(File tarFile, boolean isCreate) throws IOException {
        File dist = tarFile.getParentFile();
        if (isCreate) {
            dist = new File(tarFile.getParentFile(), FileTypeEnum.getPrefix(tarFile));
            dist.mkdirs();
        }
        unTarTo(tarFile, dist);
    }

    public static void unTarTo(String tarFile, String dist) throws IOException {
        unTarTo(new File(tarFile), new File(dist));
    }

    public static void unTarTo(File tarFile, String dist) throws IOException {
        unTarTo(tarFile, new File(dist));
    }

    public static void unTarTo(String tarFile, File dist) throws IOException {
        unTarTo(new File(tarFile), dist);
    }

    /**
     * 将tar文件解压到指定目录
     *
     * @param tarFile 要解压的tar文件
     * @param dist    解压输出的目标目录
     * @throws IOException IO异常
     */
    public static void unTarTo(File tarFile, File dist) throws IOException {
        try (FileInputStream fis = new FileInputStream(tarFile);
             BufferedInputStream bis = new BufferedInputStream(fis);
             TarArchiveInputStream tarIn = new TarArchiveInputStream(bis)) {

            TarArchiveEntry entry;
            while ((entry = tarIn.getNextTarEntry()) != null) {
                File outputFile = new File(dist, entry.getName());

                // 检查父目录并创建（若不存在）
                File parent = outputFile.getParentFile();
                if (parent != null && !parent.exists()) {
                    parent.mkdirs();
                }

                // 根据entry的类型创建文件或目录
                if (entry.isDirectory()) {
                    outputFile.mkdirs();
                } else {
                    try (FileOutputStream fos = new FileOutputStream(outputFile);
                         BufferedOutputStream bos = new BufferedOutputStream(fos)) {
                        byte[] buffer = new byte[BUFFER_SIZE];
                        int length;
                        while ((length = tarIn.read(buffer)) != -1) {
                            bos.write(buffer, 0, length);
                        }
                    }
                }
            }
        }
    }

    public static void batchUnTar(String srcPath, String targetPath) throws IOException {
        batchUnTar(new File(srcPath), new File(targetPath));
    }

    public static void batchUnTar(File srcDir, File targetDir) throws IOException {
        File[] files = srcDir.listFiles();
        for (File file : files) {
            if (file.isDirectory()) {
                batchUnTar(file, new File(targetDir, file.getName()));
            }
            if (FileTypeEnum.ZIP.is(file)) {
                unTarTo(file, targetDir);
            }
        }
    }
}
